const websocket = require('ws');
const ffmpeg = require('fluent-ffmpeg');

class KookVoice{
    constructor(wsURL, wsClient, ssrc, rtpUrl){
        this.wsURL = wsURL;
        this.client = wsClient;
        this.ssrc = ssrc;
        this.rtpUrl = rtpUrl;
        this.voiceHeartBeat = setInterval(() => {
            this.client.send('Ping');
            // console.log('[VOICE] Ping!');
        }, 30000);
    }

    static async createInstance(wsURL){
        const client = new websocket(wsURL);
        return new Promise((resolve, reject)=> {
            client.on('open', ()=>{
                // console.log("[VOICE] ws Connected!")
                Promise.all([this.init(client), this.joinVoiceChannel(client)])
                    .then(res => {
                        if(!res || !res[0]) reject("BAD RES.")
                        resolve(new KookVoice(wsURL, client, res[0].ssrc, res[0].rtpUrl));
                    })
                    .catch(err => reject(err));
            });
        });
    }
    static async getRtpCapabilities(client) {
        return new Promise(async (resolve, reject) => {
            let id = this.getRandomID();
            setTimeout(() => {
                reject('[VOICE] getRtpCapabilities timeout.');
            }, 5000);
            client.on('message', (res) => {
                let data = JSON.parse(res);
                if (data.id !== id) return;
                resolve(data);
            });
            try{
                client.send(JSON.stringify({
                    request: true,
                    id,
                    method: "getRouterRtpCapabilities",
                    data: {}
                }));
            }catch(err){
                if(err.message !== 'WebSocket is not open: readyState 0 (CONNECTING)'){
                    console.log(err);
                    return;
                }
                await this.delay(100);
                this.getRtpCapabilities(client);
            };
        });
    }
    static async createPlainTransport(client) {
        return new Promise(async (resolve, reject) => {
            let id = this.getRandomID();
            setTimeout(() => {
                reject('[VOICE] createPlainTransport timeout.');
            }, 5000);
            client.on('message', (res) => {
                let data = JSON.parse(res);
                if (data.id !== id) return;
                if(!data.data || !data.data.id) return;
                this.transportId = data.data.id;
                let rtpURL = `rtp://${data.data.ip}:${data.data.port}?rtcpport=${data.data.rtcpPort}`;
                resolve(rtpURL);
            });
            client.send(JSON.stringify({
                request: true,
                id,
                method: "createPlainTransport",
                data: {
                    forceTcp: false,
                    producing: true,
                    consuming: false,
                    sctpCapabilities: {
                        numStreams: {
                            OS: 1024,
                            MIS: 1024
                        }
                    }
                }
            }));
        });
    }
    static async produceVoice(client) {
        return new Promise(async (resolve, reject) => {
            let ssrc = this.getRandomID();
            let id = this.getRandomID();
            setTimeout(() => {
                reject('[VOICE] produceVoice timeout.');
            }, 5000);
            client.on('message', (res) => {
                let data = JSON.parse(res);
                // if (!data.data||!data.data.producerId) return;
                if (data.id !== id) return;
                // this.producerId = data.data.producerId;
                resolve(ssrc);
            });
            client.send(JSON.stringify({
                request: true,
                id,
                method: "produce",
                data: {
                    transportId: this.transportId,
                    kind: "audio",
                    rtpParameters: {
                        mid: "0",
                        codecs: [{
                            mimeType: "audio/opus",
                            payloadType: 111,
                            clockRate: 48000,
                            channels: 2,
                            parameters: {
                                minptime: 10,
                                useinbandfec: 1
                            },
                            rtcpFeedback: [{
                                type: "transport-cc",
                                parameter: ""
                            }]
                        }],
                        encodings: [{
                            ssrc,
                            dtx: false
                        }]
                    },
                    appData: {}
                }
            }));
        });
    }
    static getRandomID() {
        return Math.floor(10000000 * Math.random());
    }

    static async init(client){
        return new Promise(async (resolve, reject) => {
            this.getRtpCapabilities(client)
                .then(()=>{
                    this.createPlainTransport(client)
                        .then(rtpUrl=>{
                            this.produceVoice(client)
                                .then(ssrc=>{
                                    resolve({rtpUrl, ssrc});
                                })
                                .catch(err=>reject(err));
                        })
                        .catch(err=>reject(err));
                })
                .catch(err=>reject(err));
        });
    }

    static async joinVoiceChannel(client) {
        return new Promise(async (resolve, reject) => {
            let id = this.getRandomID();
            setTimeout(() => {
                reject('[VOICE] joinVoiceChannel timeout.');
            }, 5000);
            client.on('message', (res) => {
                let data = JSON.parse(res);
                if (data.id !== id) return;
                resolve();
            });
            client.send(JSON.stringify({
                request: true,
                id,
                method: "join",
                data: {
                    displayName: ""
                }
            }));
        })
    }
    async exitVoiceChannel() {
        clearInterval(this.voiceHeartBeat);
        return new Promise(async (resolve, reject) => {
            let id = KookVoice.getRandomID();
            setTimeout(() => {
                reject('[VOICE] exitVoiceChannel timeout.');
            }, 5000);
            this.client.send(JSON.stringify({
                request: true,
                id,
                method: "exit",
                data: {}
            }));
            await KookVoice.delay(50);
            await this.client.close();
            resolve();
        })
    }

    static async delay(ms){
        return new Promise(resolve=>setTimeout(resolve,ms));
    }


    
}

class KookVoice1{

    constructor(voiceInstance){
        this.Voice = voiceInstance;
        ffmpeg.setFfmpegPath('./ffmpeg');
    }
    
    static async createInstance(wsURL){
        return new Promise((resolve, reject) => {
            KookVoice.createInstance(wsURL)
                .then((voiceInstance)=> {
                    resolve(new KookVoice(voiceInstance));
                })
                .catch(err => reject(err));
        });
    }
    
    async startStream(path, on_open, on_end, on_err) {
        path = path ?? './test.mp3';
        this.stream = new ffmpeg()
            .input(path)
            .inputOptions(['-re'])
            .audioFilters('volume=0.5')
            .outputOptions([
                '-map 0:a:0',
                '-bufsize 64k',
                '-threads 4'
              ])
            .audioCodec('libopus')
            .audioBitrate('320k')
            .audioChannels(2)
            .audioFrequency(48000)
            .outputFormat('tee')
            .removeAllListeners('start')
            .removeAllListeners('error')
            .removeAllListeners('end');

        this.stream.on('start', (commandLine) => {
            this.streaming = true;
            console.log('[VOICE] stream started!');
            on_open ||= () => {};
            on_open;
        });
        this.stream.on('error', async (e) => {
            console.log('[VOICE] stream ' + e);
            await this.Voice.exitVoiceChannel();
            this.streaming = false;
            on_err ||= () => {};
            on_err;
        });
        this.stream.on('end', async () => {
            console.log('[VOICE] stream ended!');
            await this.Voice.exitVoiceChannel();
            this.streaming = false;
            on_end ||= () => {};
            on_end;
        });

        this.stream.save(`[select=a:f=rtp:ssrc=${this.Voice.ssrc}:payload_type=111]${this.Voice.rtpUrl}`);
    }

    async stopStream() {
        if (this.streaming) { 
            await this.stream.kill('SIGKILL');
        }
    }

    async getMusicByParams(input_path, output_path, paramArr){
        return new Promise((resolve, reject)=>{
            const converter = this.ffmpeg()
            .input(input_path)
            .inputOptions()
            .audioFilters('atempo=' + speed)
            .outputOptions([
                '-filter:a',
                '-map 0:a:0',
                '-bufsize 64k',
                '-preset veryfast',
                '-threads 4'
              ])
            .audioCodec('libopus')
            .audioBitrate('320k')
            .audioChannels(2)
            .audioFrequency(48000)
            .outputFormat('opus')
            .removeAllListeners('start')
            .removeAllListeners('error')
            .removeAllListeners('end')
            .on('start', (commandLine) => {
                console.log('[VOICE] stream started');
                console.log('Spawned Ffmpeg with command: ' + commandLine);
            })
            .on('error', (e) => {
                console.log('[VOICE] stream Err' + e);
                reject();
            })
            .on('end', (e) => {
                console.log('[VOICE] stream End' + e);
                resolve();
            });

        converter.save(output_path);

        })
        
    };

}

module.exports = KookVoice;
